module mcp_blk #(parameter DATA_WIDTH = 8)
(
  output                  aready,  // ready to receive next data  
  input  [DATA_WIDTH-1:0] adatain,
  input                   asend,
  input                   aclk, arst_n,
  output [DATA_WIDTH-1:0] bdata,
  output                  bvalid,  // bdata valid (ready)  
  input                   bload,  
  input                   bclk, brst_n);  

wire [DATA_WIDTH-1:0] adata;   // internal data bus  
wire                  b_ack;   // acknowledge enable signal  
wire                  a_en;    // control enable signal  
wire                  bq2_en;  // control  - sync output  
wire                  aq2_ack; // feedback - sync output  

sync2 async  (.q(aq2_ack), .d(b_ack), .clk(aclk), .rst_n(arst_n));  
sync2 bsync  (.q(bq2_en),  .d(a_en),  .clk(bclk), .rst_n(brst_n));  

amcp_send #(.DATA_WIDTH(DATA_WIDTH)) alogic 
(  
  .adata          (adata),
  .a_en           (a_en),
  .aready         (aready),
  .adatain        (adatain),
  .asend          (asend),
  .aq2_ack        (aq2_ack),
  .aclk           (aclk), 
  .arst_n         (arst_n)
);  
bmcp_recv #(.DATA_WIDTH(DATA_WIDTH)) blogic 
(  
  .bdata          (bdata),  
  .bvalid         (bvalid), // bdata valid  
  .b_ack          (b_ack),  // acknowledge signal  
  .adata          (adata),  // unsynchronized adata  
  .bload          (bload),  // load data and acknowledge receipt  
  .bq2_en         (bq2_en), // synchornized enable input  
  .bclk           (bclk),
  .brst_n         (brst_n));

endmodule


// sync signal to different clock domain
module sync2 (
  output q,
  input  d, clk, rst_n
);  

wire q1; // 1st stage ff output  
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) {q,q1} <= '0;
  else
    {q,q1} <= {q1,d};

endmodule

// Pulse Generator
module plsgen (
  output     pulse, 
  output reg q,  
  input      d,  
  input      clk,
  input      rst_n
);  

always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) q <= '0;
  else        q <= d;  
  
assign pulse = q ^ d;

endmodule


module asend_fsm (  
  output aready,  // ready to send next data  
  input  asend,   // send adata  
  input  aack,    // acknowledge receipt of adata  
  input  aclk, arst_n
);  

enum logic {READY = '1, 
            BUSY  = '0} state, next;  

always_ff @(posedge aclk or negedge arst_n)    
  if (!arst_n) state <= READY;    
  else         state <= next;  
  
always_comb begin    
  case (state)      
    READY: if (asend) next = BUSY;             
           else       next = READY;      
    BUSY : if (aack)  next = READY;
           else       next = BUSY;
  endcase  
end  

assign aready = state;

endmodule


module back_fsm (  
  output bvalid,  // data valid / ready to load  
  input  bload,   // load data / send acknowledge  
  input  b_en,    // enable receipt of adata  
  input  bclk, brst_n
);  

enum logic {READY = '1, 
            WAIT  = '0} state, next;  

always_ff @(posedge bclk or negedge brst_n)    
  if (!brst_n) state <= WAIT;    
  else         state <= next;  

always_comb begin    
  case (state)      
    READY: if (bload) next = WAIT;             
    else       next = READY;      
    WAIT : if (b_en)  next = READY;             
    else       next = WAIT;    
  endcase  
end  

assign bvalid = state;

endmodule


module bmcp_recv #(parameter DATA_WIDTH = 8)
(  
  output reg [DATA_WIDTH-1:0] bdata,  
  output                      bvalid, // bdata valid  
  output reg                  b_ack,  // acknowledge signal  
  input      [DATA_WIDTH-1:0] adata,  // unsynchronized adata  
  input                       bload,  // load data and acknowledge receipt  
  input                       bq2_en, // synchornized enable input  
  input                       bclk, 
  input                       brst_n);  

wire b_en;  // enable pulse from pulse generator  

// Pulse Generator  
plsgen pg1 (.pulse(b_en), .q(), .d(bq2_en), .clk(bclk), .rst_n(brst_n), .*);  

// data ready/acknowledge FSM  
back_fsm fsm(  
  .bvalid   (bvalid),
  .bload    (bload),
  .b_en     (b_en),
  .bclk     (bclk),
  .brst_n   (brst_n)
);  

// load next data word  
wire bload_data = bvalid & bload;  

// toggle-flop controlled by bload_data  
always_ff @(posedge bclk or negedge brst_n)    
  if      (   !brst_n) b_ack <= '0;    
  else if (bload_data) b_ack <= ~b_ack;  

always_ff @(posedge bclk or negedge brst_n)    
  if      (   !brst_n) bdata <= '0;    
  else if (bload_data) bdata <= adata;

endmodule


module amcp_send #(parameter DATA_WIDTH = 8)
(  
  output reg [DATA_WIDTH-1:0] adata,  
  output reg                  a_en, 
  output                      aready,  
  input      [DATA_WIDTH-1:0] adatain,  
  input                       asend,  
  input                       aq2_ack,  
  input                       aclk,
  input                       arst_n
);  

wire aack; // acknowledge pulse from pulse generator  

// Pulse Generator  
plsgen pg1 (.pulse(aack), .q(), .d(aq2_ack), .clk(aclk), .rst_n(arst_n));  

// data ready/acknowledge FSM  
asend_fsm fsm(  
  .aready   (aready),
  .asend    (asend),
  .aack     (aack),
  .aclk     (aclk),
  .arst_n   (arst_n)
);  


// send next data word  
assign anxt_data = aready & asend;  

// toggle-flop controlled by anxt_data  
always_ff @(posedge aclk or negedge arst_n)    
  if      (  !arst_n) a_en <= '0;    
  else if (anxt_data) a_en <= ~a_en;  


always_ff @(posedge aclk or negedge arst_n)    
  if      (  !arst_n) adata <= '0;    
  else if (anxt_data) adata <= adatain;

endmodule
